/*
 * This program loads the password and group databases into the kernel
 * for NFS V4. It should probably be replaced by nfssvc(2) calls used
 * by the password and group database maintenance programs, whatever
 * those might be.
 */
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pwd.h>
#include <grp.h>
#include <netdb.h>
#include <syslog.h>
#include <sys/param.h>
#include <sys/ucred.h>
#include <sys/time.h>
#include <sys/mount.h>
#include <sys/socket.h>
#include <sys/socketvar.h>
#include <sys/errno.h>
#include <sys/vnode.h>

#include <rpc/rpc.h>
#include <rpc/pmap_clnt.h>
#ifdef notdef
#include <rpc/pmap_prot.h>
#endif
#include <newnfs/nfs/nfsproto.h>
#include <newnfs/nfs/rpcv2.h>
#include <newnfs/nfs/nfs.h>

#ifdef DARWIN
#define	setproctitle(p)
#endif

void	cleanup_term __P((int));
void	usage __P((void));
void	nfsuserdsrv __P((struct svc_req *, SVCXPRT *));
bool_t	xdr_getid __P((XDR *, caddr_t));
bool_t	xdr_getname __P((XDR *, caddr_t));
bool_t	xdr_retval __P((XDR *, caddr_t));

#define	MAXNAME		1024
#define	MAXNFSUSERD	20
#define	DEFNFSUSERD	4
#define	DEFUSERMAX	200
#define	DEFUSERTIMEOUT	(60 * 60)
struct info {
	long	id;
	long	retval;
	char	name[MAXNAME + 1];
};

u_char *dnsname = "default.domain";
u_char *defaultuser = "nobody";
uid_t defaultuid = (uid_t)32767;
u_char *defaultgroup = "nogroup";
gid_t defaultgid = (gid_t)32767;
int verbose = 0, im_a_slave = 0, nfsuserdcnt = -1, forcestart = 0, kext = 0, cbd = 0;
int defusertimeout = DEFUSERTIMEOUT, iod = 0;
pid_t slaves[MAXNFSUSERD], cbdpid = 0, iodpid = 0;

/*
 * Arguments for kextload and nfscbd.
 */
char *kextpath = "/sbin/kextload";
char *kextargv[4] = {
	"kextload",
	"-q",
	"/Library/Filesystems/ca.uoguelph.newnfs.fs/Support/newnfs.kext",
	NULL
};
char *nfscbdpath = "/Library/Filesystems/ca.uoguelph.newnfs.fs/Support/nfscbd";
char *nfscbdargv[2] = {
	"nfscbd",
	NULL
};
char *nfsiodpath = "/Library/Filesystems/ca.uoguelph.newnfs.fs/Support/newnfsiod";
char *nfsiodargv[2] = {
	"newnfsiod",
	NULL
};

int
main(argc, argv, envp)
	int argc;
	char *argv[], *envp[];
{
	register int i;
	int error, len;
	struct nfsd_idargs nid;
	struct passwd *pwd;
	struct group *grp;
	struct hostent *hp;
	int sock, one = 1, childstat;
	SVCXPRT *udptransp, *tcptransp;
	struct passwd *pw;
	u_short portnum;
	sigset_t signew;
	char hostname[MAXHOSTNAMELEN + 1], *cp, **aliases;

#if defined(__FreeBSD_version) && __FreeBSD_version >= 500000
	if (modfind("nfsd") < 0) {
		/* Not present in kernel, try loading it */
		if (kldload("nfsd") < 0 || modfind("nfsd") < 0)
			errx(1, "NFSD server is not available");
	}
#endif

	/*
	 * First, figure out what our domain name and Kerberos Realm
	 * seem to be. Command line args may override these later.
	 */
	if (gethostname(hostname, MAXHOSTNAMELEN) == 0) {
		if ((cp = strchr(hostname, '.')) != NULL &&
			*(cp + 1)) {
			dnsname = cp + 1;
		} else {
			hp = gethostbyname(hostname);
			if (hp) {
			    if ((cp = strchr(hp->h_name, '.')) != NULL &&
				*(cp + 1)) {
				dnsname = cp + 1;
			    } else {
				aliases = hp->h_aliases;
				while (*aliases) {
				    if ((cp = strchr(*aliases, '.')) != NULL &&
					*(cp + 1)) {
					dnsname = cp + 1;
					break;
				    }
				    aliases++;
				}
			    }
			}
		}
	}
	nid.nid_usermax = DEFUSERMAX;
	nid.nid_usertimeout = defusertimeout;

	argc--;
	argv++;
	while (argc >= 1) {
		if (!strcmp(*argv, "-domain")) {
			if (argc == 1)
				usage();
			argc--;
			argv++;
			strncpy(hostname, *argv, MAXHOSTNAMELEN);
			hostname[MAXHOSTNAMELEN] = '\0';
			dnsname = hostname;
		} else if (!strcmp(*argv, "-verbose")) {
			verbose = 1;
		} else if (!strcmp(*argv, "-force")) {
			forcestart = 1;
#ifdef DARWIN
		} else if (!strcmp(*argv, "-kext")) {
			kext = 1;
#endif
		} else if (!strcmp(*argv, "-cbd")) {
			cbd = 1;
		} else if (!strcmp(*argv, "-iod")) {
			iod = 1;
		} else if (!strcmp(*argv, "-usermax")) {
			if (argc == 1)
				usage();
			argc--;
			argv++;
			i = atoi(*argv);
			if (i < 10 || i > 100000) {
				fprintf(stderr,
				    "usermax out of range 10<->100000\n", i);
				usage();
			}
			nid.nid_usermax = i;
		} else if (!strcmp(*argv, "-usertimeout")) {
			if (argc == 1)
				usage();
			argc--;
			argv++;
			i = atoi(*argv);
			if (i < 0 || i > 100000) {
				fprintf(stderr,
				    "usertimeout out of range 0<->100000\n", i);
				usage();
			}
			nid.nid_usertimeout = defusertimeout = i * 60;
		} else if (nfsuserdcnt == -1) {
			nfsuserdcnt = atoi(*argv);
			if (nfsuserdcnt < 1)
				usage();
			if (nfsuserdcnt > MAXNFSUSERD) {
				warnx("nfsuserd count %d; reset to %d",
				    nfsuserdcnt, DEFNFSUSERD);
				nfsuserdcnt = DEFNFSUSERD;
			}
		} else {
			usage();
		}
		argc--;
		argv++;
	}
	if (nfsuserdcnt < 1)
		nfsuserdcnt = DEFNFSUSERD;

	/*
	 * Strip off leading and trailing '.'s in domain name and map
	 * alphabetics to lower case.
	 */
	while (*dnsname == '.')
		dnsname++;
	if (*dnsname == '\0') {
		fprintf(stderr, "Domain name all '.'\n");
		exit(1);
	}
	len = strlen(dnsname);
	cp = dnsname + len - 1;
	while (*cp == '.') {
		*cp = '\0';
		len--;
		cp--;
	}
	for (i = 0; i < len; i++) {
		if (!isascii(dnsname[i])) {
			fprintf(stderr, "Domain name has non-ascii char\n");
			exit(1);
		}
		if (isupper(dnsname[i]))
			dnsname[i] = tolower(dnsname[i]);
	}

	/* For -kext, fork a child to exec kextload. */
	if (kext > 0) {
		if (vfork() == 0) {
			execv(kextpath, kextargv);
			exit(1);
		}
		wait(&childstat);
		if (childstat != 0)
			fprintf(stderr,"kextload wouldn't\n");
	}

	/*
	 * If the nfsuserd died off ungracefully, this is necessary to
	 * get them to start again.
	 */
	if (forcestart && nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0) {
		syslog(LOG_ERR, "Can't do nfssvc() to delete the port\n");
		exit(1);
	}

	if (verbose)
	    fprintf(stderr, "nfsuserd: domain=%s usermax=%d usertimeout=%d\n",
		dnsname, nid.nid_usermax, nid.nid_usertimeout);

	for (i = 0; i < nfsuserdcnt; i++)
		slaves[i] = (pid_t)-1;

	/*
	 * Set up the service port to accept requests via UDP from
	 * localhost (127.0.0.1).
	 */
	if ((sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)) < 0) {
		fprintf(stderr, "cannot create udp socket\n");
		exit(1);
	}

	/*
	 * Not sure what this does, so I'll leave it here for now.
	 */
	setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one));
	
	if ((udptransp = svcudp_create(sock)) == NULL) {
		fprintf(stderr, "Can't set up socket\n");
		exit(1);
	}

	/*
	 * By not specifying a protocol, it is linked into the
	 * dispatch queue, but not registered with portmapper,
	 * which is just what I want.
	 */
	if (!svc_register(udptransp, RPCPROG_NFSUSERD, RPCNFSUSERD_VERS,
		nfsuserdsrv, 0)) {
		fprintf(stderr, "Can't register nfsuserd\n");
		exit(1);
	}

	/*
	 * Tell the kernel what my port# is.
	 */
	portnum = htons(udptransp->xp_port);
#ifdef DEBUG
	printf("portnum=0x%x\n", portnum);
#else
	if (nfssvc(NFSSVC_NFSUSERDPORT, (caddr_t)&portnum) < 0) {
	    if (errno == EPERM) {
		fprintf(stderr, "Can't start nfsuserd when already running");
		fprintf(stderr, " If not running, use the -force option.\n");
	    } else {
		fprintf(stderr, "Can't do nfssvc() to add port\n");
	    }
	    exit(1);
	}
#endif

	pwd = getpwnam(defaultuser);
	if (pwd)
		nid.nid_uid = pwd->pw_uid;
	else
		nid.nid_uid = defaultuid;
	grp = getgrnam(defaultgroup);
	if (grp)
		nid.nid_gid = grp->gr_gid;
	else
		nid.nid_gid = defaultgid;
	nid.nid_name = dnsname;
	nid.nid_namelen = strlen(nid.nid_name);
	nid.nid_flag = NFSID_INITIALIZE;
#ifdef DEBUG
	printf("Initialize uid=%d gid=%d dns=%s\n",nid.nid_uid,nid.nid_gid,
		nid.nid_name);
#else
	error = nfssvc(NFSSVC_IDNAME, &nid);
	if (error) {
		fprintf(stderr, "Can't initialize nfs user/groups\n");
		exit(1);
	}
#endif

	i = 0;
	/*
	 * Loop around adding all groups.
	 */
	setgrent();
	while (i < nid.nid_usermax && (grp = getgrent())) {
		nid.nid_gid = grp->gr_gid;
		nid.nid_name = grp->gr_name;
		nid.nid_namelen = strlen(grp->gr_name);
		nid.nid_flag = NFSID_ADDGID;
#ifdef DEBUG
		printf("add gid=%d name=%s\n",nid.nid_gid,nid.nid_name);
#else
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
			fprintf(stderr, "Can't add group %s\n", grp->gr_name);
			exit(1);
		}
#endif
		i++;
	}

	/*
	 * Loop around adding all users.
	 */
	setpwent();
	while (i < nid.nid_usermax && (pwd = getpwent())) {
		nid.nid_uid = pwd->pw_uid;
		nid.nid_name = pwd->pw_name;
		nid.nid_namelen = strlen(pwd->pw_name);
		nid.nid_flag = NFSID_ADDUID;
#ifdef DEBUG
		printf("add uid=%d name=%s\n",nid.nid_uid,nid.nid_name);
#else
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
			fprintf(stderr, "Can't add user %s\n", pwd->pw_name);
			exit(1);
		}
#endif
		i++;
	}

#ifdef DEBUG
	exit(0);
#endif
#ifndef DARWIN
	daemon(0, 0);
	(void)signal(SIGTERM, SIG_IGN);
#endif
	(void)signal(SIGHUP, SIG_IGN);
	(void)signal(SIGINT, SIG_IGN);
	(void)signal(SIGQUIT, SIG_IGN);
	(void)signal(TERMSIG, cleanup_term);
	(void)signal(SIGCHLD, cleanup_term);

	openlog("nfsuserd:", LOG_PID, LOG_DAEMON);

	/* If -cbd, start the nfscbd master as a child */
	if (cbd > 0) {
		cbdpid = vfork();
		if (cbdpid == 0) {
			execv(nfscbdpath, nfscbdargv);
			exit(1);
		}
	}

	/* If -iod, start the newnfsiod master as a child */
	if (iod > 0) {
		iodpid = vfork();
		if (iodpid == 0) {
			execv(nfsiodpath, nfsiodargv);
			exit(1);
		}
	}

	/*
	 * For one nfsuserd, just do it.
	 */
	if (nfsuserdcnt == 1) {
		svc_run();
		syslog(LOG_ERR, "nfsuserd died: %m");
		exit(1);
	}

	/*
	 * Temporarily block TERMSIG and SIGCHLD, so slaves[] can't
	 * end up bogus.
	 */
	sigemptyset(&signew);
	sigaddset(&signew, TERMSIG);
	sigaddset(&signew, SIGCHLD);
	sigprocmask(SIG_BLOCK, &signew, NULL);

	/*
	 * Fork off the slave daemons that do the work. All the master
	 * does is kill them off and cleanup.
	 */
	for (i = 0; i < nfsuserdcnt; i++) {
	    slaves[i] = fork();
	    if (slaves[i] == 0) {
		im_a_slave = 1;
		setproctitle("slave");
		sigemptyset(&signew);
		sigaddset(&signew, TERMSIG);
		sigprocmask(SIG_UNBLOCK, &signew, NULL);

		/*
		 * and away we go.
		 */
		svc_run();
		syslog(LOG_ERR, "nfsuserd died: %m");
		exit(1);
	    } else if (slaves[i] < 0) {
		syslog(LOG_ERR, "fork: %m");
	    }
	}

	/*
	 * Just wait for TERMSIG or a child to die and then...
	 * As the Governor of California would say, "Terminate them".
	 */
	setproctitle("master");
	sigemptyset(&signew);
	while (1)
		sigsuspend(&signew);
}

/*
 * The nfsuserd rpc service
 */
void
nfsuserdsrv(rqstp, transp)
	struct svc_req *rqstp;
	SVCXPRT *transp;
{
	register int i;
	register char *cp;
	register struct passwd *pwd;
	register struct group *grp;
	int error;
	u_short sport;
	struct info info;
	struct nfsd_idargs nid;
	u_int32_t saddr;

	/*
	 * Only handle requests from 127.0.0.1 on a reserved port number.
	 * (Since a reserved port # at localhost implies a client with
	 *  local root, there won't be a security breach. This is about
	 *  the only case I can think of where a reserved port # means
	 *  something.)
	 */
	sport = ntohs(transp->xp_raddr.sin_port);
	saddr = ntohl(transp->xp_raddr.sin_addr.s_addr);
	if ((rqstp->rq_proc != NULLPROC && sport >= IPPORT_RESERVED) ||
		saddr != 0x7f000001) {
		syslog(LOG_ERR, "req from ip=0x%x port=%d\n", saddr, sport);
		svcerr_weakauth(transp);
		return;
	}
	switch (rqstp->rq_proc) {
	case NULLPROC:
		if (!svc_sendreply(transp, (xdrproc_t)xdr_void, NULL))
			syslog(LOG_ERR, "Can't send reply");
		return;
	case RPCNFSUSERD_GETUID:
		if (!svc_getargs(transp, (xdrproc_t)xdr_getid, (caddr_t)&info)) {
			svcerr_decode(transp);
			return;
		}
		pwd = getpwuid((uid_t)info.id);
		info.retval = 0;
		if (pwd) {
			nid.nid_usertimeout = defusertimeout;
			nid.nid_uid = pwd->pw_uid;
			nid.nid_name = pwd->pw_name;
		} else {
			nid.nid_usertimeout = 5;
			nid.nid_uid = (uid_t)info.id;
			nid.nid_name = defaultuser;
		}
		nid.nid_namelen = strlen(nid.nid_name);
		nid.nid_flag = NFSID_ADDUID;
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
		    info.retval = error;
		    syslog(LOG_ERR,"Can't add user %s\n",pwd->pw_name);
		} else if (verbose) {
		    syslog(LOG_ERR,"Added uid=%d name=%s\n",
			nid.nid_uid, nid.nid_name);
		}
		if (!svc_sendreply(transp, (xdrproc_t)xdr_retval, (caddr_t)&info))
			syslog(LOG_ERR, "Can't send reply");
		return;
	case RPCNFSUSERD_GETGID:
		if (!svc_getargs(transp, (xdrproc_t)xdr_getid, (caddr_t)&info)) {
			svcerr_decode(transp);
			return;
		}
		grp = getgrgid((gid_t)info.id);
		info.retval = 0;
		if (grp) {
			nid.nid_usertimeout = defusertimeout;
			nid.nid_gid = grp->gr_gid;
			nid.nid_name = grp->gr_name;
		} else {
			nid.nid_usertimeout = 5;
			nid.nid_gid = (gid_t)info.id;
			nid.nid_name = defaultgroup;
		}
		nid.nid_namelen = strlen(nid.nid_name);
		nid.nid_flag = NFSID_ADDGID;
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
		    info.retval = error;
		    syslog(LOG_ERR, "Can't add group %s\n",
			grp->gr_name);
		} else if (verbose) {
		    syslog(LOG_ERR,"Added gid=%d name=%s\n",
			nid.nid_gid, nid.nid_name);
		}
		if (!svc_sendreply(transp, (xdrproc_t)xdr_retval, (caddr_t)&info))
			syslog(LOG_ERR, "Can't send reply");
		return;
	case RPCNFSUSERD_GETUSER:
		if (!svc_getargs(transp, (xdrproc_t)xdr_getname, (caddr_t)&info)) {
			svcerr_decode(transp);
			return;
		}
		pwd = getpwnam(info.name);
		info.retval = 0;
		if (pwd) {
			nid.nid_usertimeout = defusertimeout;
			nid.nid_uid = pwd->pw_uid;
			nid.nid_name = pwd->pw_name;
		} else {
			nid.nid_usertimeout = 5;
			nid.nid_uid = defaultuid;
			nid.nid_name = info.name;
		}
		nid.nid_namelen = strlen(nid.nid_name);
		nid.nid_flag = NFSID_ADDUSERNAME;
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
		    info.retval = error;
		    syslog(LOG_ERR,"Can't add user %s\n",pwd->pw_name);
		} else if (verbose) {
		    syslog(LOG_ERR,"Added uid=%d name=%s\n",
			nid.nid_uid, nid.nid_name);
		}
		if (!svc_sendreply(transp, (xdrproc_t)xdr_retval, (caddr_t)&info))
			syslog(LOG_ERR, "Can't send reply");
		return;
	case RPCNFSUSERD_GETGROUP:
		if (!svc_getargs(transp, (xdrproc_t)xdr_getname, (caddr_t)&info)) {
			svcerr_decode(transp);
			return;
		}
		grp = getgrnam(info.name);
		info.retval = 0;
		if (grp) {
			nid.nid_usertimeout = defusertimeout;
			nid.nid_gid = grp->gr_gid;
			nid.nid_name = grp->gr_name;
		} else {
			nid.nid_usertimeout = 5;
			nid.nid_gid = defaultgid;
			nid.nid_name = info.name;
		}
		nid.nid_namelen = strlen(nid.nid_name);
		nid.nid_flag = NFSID_ADDGROUPNAME;
		error = nfssvc(NFSSVC_IDNAME, &nid);
		if (error) {
		    info.retval = error;
		    syslog(LOG_ERR, "Can't add group %s\n",
			grp->gr_name);
		} else if (verbose) {
		    syslog(LOG_ERR,"Added gid=%d name=%s\n",
			nid.nid_gid, nid.nid_name);
		}
		if (!svc_sendreply(transp, (xdrproc_t)xdr_retval, (caddr_t)&info))
			syslog(LOG_ERR, "Can't send reply");
		return;
	default:
		svcerr_noproc(transp);
		return;
	};
}

/*
 * Xdr routine to get an id number
 */
bool_t
xdr_getid(xdrsp, cp)
	XDR *xdrsp;
	caddr_t cp;
{
	register struct info *ifp = (struct info *)cp;

	return (xdr_long(xdrsp, &ifp->id));
}

/*
 * Xdr routine to get a user name
 */
bool_t
xdr_getname(xdrsp, cp)
	XDR *xdrsp;
	caddr_t cp;
{
	register struct info *ifp = (struct info *)cp;
	long len;

	if (!xdr_long(xdrsp, &len))
		return (0);
	if (len > MAXNAME)
		return (0);
	if (!xdr_opaque(xdrsp, ifp->name, len))
		return (0);
	ifp->name[len] = '\0';
	return (1);
}

/*
 * Xdr routine to return the value.
 */
bool_t
xdr_retval(xdrsp, cp)
	XDR *xdrsp;
	caddr_t cp;
{
	register struct info *ifp = (struct info *)cp;
	long val;

	val = ifp->retval;
	return (xdr_long(xdrsp, &val));
}

/*
 * cleanup_term() called via TERMSIG.
 */
void
cleanup_term(signo)
	int signo;
{
	register int i, cnt;

	if (im_a_slave)
		exit(0);

	/*
	 * Ok, so I'm the master.
	 * As the Governor of California might say, "Terminate them".
	 * (For Darwin, all children are sent the signal via killall.)
	 */
	cnt = 0;
	for (i = 0; nfsuserdcnt > 1 && i < nfsuserdcnt; i++) {
		if (slaves[i] != (pid_t)-1) {
			cnt++;
#ifndef DARWIN
			kill(slaves[i], TERMSIG);
#endif
		}
	}

	/* and the nfscbd */
	if (cbdpid != 0) {
		kill(cbdpid, SIGUSR1);
		cnt++;
	}

	/* and the newnfsiod */
	if (iodpid != 0) {
		kill(iodpid, SIGUSR1);
		cnt++;
	}

	/*
	 * and wait for them to die
	 */
	for (i = 0; i < cnt; i++)
		wait3(NULL, 0, NULL);

	/*
	 * Finally, get rid of the socket
	 */
	if (nfssvc(NFSSVC_NFSUSERDDELPORT, NULL) < 0) {
		syslog(LOG_ERR, "Can't do nfssvc() to delete the port\n");
		exit(1);
	}
	exit(0);
}

void
usage()
{
	fprintf(stderr,
	    "usage: nfsuserd [-kext] [-cbd] [-iod] [-usermax cache_size] [-usertimeout minutes] [-verbose] [-domain domain_name] [n]\n");
	exit(1);
}
